/** 多线程同步对象，采用CRITICAL_SECTION实现，适用于单个进程内同步，不能跨进程，但效率较高
 * @file lock.hpp
 * @author Kiven Lee
 * @version 1.0
 * @date 2021-11-17
 *
 * @copyright Kivensoft (c) 2018 - 2021
 *
 */

#pragma once

#include <winsock2.h>
#include <windows.h>
#include <memory>
#include "../klib/utils.hpp"

namespace kwin {

    /** 在gui模式，打开控制台窗口，用于输出调试日志 */
    static inline void open_console() {
        ::AllocConsole();
        ::AttachConsole(::GetCurrentProcessId());
        FILE *stream;
        ::freopen_s(&stream, "CONOUT$", "w+", stdout);
        ::freopen_s(&stream, "CONOUT$", "w+", stderr);
        ::SetConsoleOutputCP(65001);
    }

    /** 线程互斥对象，用于线程数据同步 */
    class app_lock_t {
    private:
        CRITICAL_SECTION cs;

    public:
        app_lock_t() { ::InitializeCriticalSection(&cs); }
        ~app_lock_t() { ::DeleteCriticalSection(&cs); }

        /** 进入临界区，进入临界区后，其它线程调用此函数将等待，直到本线程调用leave离开临界区 */
        void enter() { ::EnterCriticalSection(&cs); }

        /** 离开临界区，离开临界区后其它等待进入临界区的线程可激活执行 */
        void leave() { ::LeaveCriticalSection(&cs); }
    };

    /** 函数结束时自动离开临界区的类 */
    class app_lock_auto_t {
    private:
        app_lock_t* p_app_lock;

    public:
        app_lock_auto_t(app_lock_t& app_lock):p_app_lock(&app_lock) { app_lock.enter(); }
        ~app_lock_auto_t() { p_app_lock->leave(); }

        app_lock_auto_t(const app_lock_auto_t& v) = delete;
        app_lock_auto_t& operator = (const app_lock_auto_t& v) = delete;
        void* operator new(size_t t) = delete;
    };

    /** 将命令行参数转换为传统的argc、argv格式
     *
     * @param out 输出缓冲区
     * @return int 输出命令行参数数量，命令行实际值char* argv[]在out.data()中
     */
    static inline int CmdLineToArgv(klib::dstring_t<256>& out) {
        // 获取命令行参数的宽字符版本
        int nArgs;
        LPWSTR * argListPtr = ::CommandLineToArgvW(GetCommandLineW(), &nArgs);
        auto alDeleter  = [](LPWSTR *v) { ::LocalFree((HLOCAL)v); };
        std::unique_ptr<LPWSTR, decltype(alDeleter)> argList(argListPtr, alDeleter);
        if (!argListPtr) return 0;

        // 将宽字符版本参数列表转为utf8版本参数列表
        char * arg = out.data() + sizeof(char *) * nArgs;
        for (int i = 0; i < nArgs; i++) {
            int argLen = ::WideCharToMultiByte(CP_ACP, 0, argListPtr[i], -1, NULL, 0, 0, 0) + 1;
            // 如果命令行缓冲区buf大小不足，则重新分配大小
            while (arg + argLen >= out.end()) {
                out.resize(out.size() << 1);
                arg = out.data() + sizeof(char *) * nArgs;
            }

            // 将宽字符参数转换成utf8
            ::WideCharToMultiByte(CP_ACP, 0, argListPtr[i], -1, arg, argLen, 0, 0);
            // 设置参数列表数组对应的地址
            ((char **)out.data())[i] = arg;
            // 移动当前缓冲区指针到下一个参数写入位置
            arg += argLen;
        }
        return nArgs;
    }

    struct WndClass : WNDCLASSEXA {
        WndClass(HINSTANCE hInstance, LPCSTR className, WNDPROC wndProc, HICON icon = NULL, LPCSTR menu = NULL) {
            cbSize        = sizeof(WNDCLASSEX);
            style         = CS_HREDRAW | CS_VREDRAW;
            lpfnWndProc   = wndProc;
            cbClsExtra    = 0;
            cbWndExtra    = 0;
            hInstance     = hInstance;
            hIcon         = icon;
            hCursor       = LoadCursor(NULL, IDC_ARROW),
            hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
            lpszMenuName  = NULL, // MAKEINTRESOURCE(IDC_WKEBROWSER);
            lpszClassName = className;
            hIconSm       = icon;
        }
    };

    struct Rect : public ::RECT {
        WINBOOL getClient(HWND hwnd) { return ::GetClientRect(hwnd, this); }
        LONG width() { return right - left; }
        LONG height() { return bottom - top; }
    };

    static inline void* GetWindowUserData(HWND hwnd) {
        return (void*) ::GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    }

    static inline void* SetWindowUserData(HWND hwnd, void* data) {
        return (void*) ::SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR) data);
    }

    static inline void* GetCreateParamFromLParam(LPARAM lParam) {
        return ((LPCREATESTRUCT) lParam)->lpCreateParams;
    }

} // namespace kwin
